﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace V32Assembler
{
    public abstract class Argument
    {
        /// <returns>A tuple of (reg/const, direct/indirect).</returns>
        public abstract ArgInfo GetArgType();
        public abstract byte[] GetArgData();
        public uint GetArgSize()
        {
            return (uint)GetArgData().Length;
        }
        public abstract uint GetBaseArgSize();

        public static void InferIndirectionSizes(DestArg dest, SourceArg src)
        {
            if (!dest.IndirectionSize.HasValue && !src.Indirect)
            {
                if (src.SourceType == ArgSource.Reg)
                    dest.IndirectionSize = src.RegVal.Size;
                else dest.IndirectionSize = src.NumVal.Size;
            }
            if (src.IndirectionSize.HasValue)
            {
                if (!dest.IndirectionSize.HasValue)
                    dest.IndirectionSize = src.IndirectionSize;
                return;
            }

            // if dest is direct, then src.IndirectionSize is size of dest
            if (dest.OpType == DestType.Reg)
                src.IndirectionSize = dest.RegVal.Size;
            else
            {
                // dest is indirect
                if (dest.IndirectionSize.HasValue)
                    src.IndirectionSize = dest.IndirectionSize;
                else
                    src.IndirectionSize = dest.IndirectionSize = ArgSize.Word;
            }
        }
    }

    public enum ArgSource { Reg, Const }
    public class SourceArg : Argument
    {
        public ArgSource SourceType { get; private set; }
        public bool Indirect { get; private set; }

        public BaseNumber NumVal { get; private set; }
        public Register RegVal { get; private set; }

        public bool Relative { get; private set; }
        public OffsetNumber Offset { get; private set; }

        public ArgSize? IndirectionSize { get; set; }

        private SourceArg(ArgSource sourceType, bool indirect, BaseNumber num, Register reg, bool relative, OffsetNumber offset, ArgSize? indirectionSize)
        {
            this.SourceType = sourceType;
            this.Indirect = indirect;

            this.NumVal = num;
            this.RegVal = reg;

            this.Relative = relative;
            this.Offset = offset;

            this.IndirectionSize = indirectionSize;
        }

        public SourceArg(BaseNumber constVal, bool indirect, ArgSize? indirectionSize)
            : this(ArgSource.Const, indirect, constVal, Register.None, false, new OffsetNumber(), indirectionSize) { }
        public SourceArg(Register reg, bool indirect, OffsetNumber offset, ArgSize? indirectionSize)
            : this(ArgSource.Reg, indirect, new BaseNumber(), reg, true, offset, indirectionSize) { }

        public SourceArg(Register reg, bool indirect, ArgSize? indirectionSize)
            : this(ArgSource.Reg, indirect, new BaseNumber(), reg, false, new OffsetNumber(), indirectionSize) { }
        public SourceArg(BaseNumber constVal, bool indirect, OffsetNumber offset, ArgSize? indirectionSize)
            : this(ArgSource.Const, indirect, constVal, Register.None, true, offset, indirectionSize) { }

        public override ArgInfo GetArgType()
        {
            if (Relative)
                return new ArgInfo(SourceType == ArgSource.Const, Indirect, Offset);
            else return new ArgInfo(SourceType == ArgSource.Const, Indirect);
        }
        public override byte[] GetArgData()
        {
            List<byte> bytes = new List<byte>();

            if (SourceType == ArgSource.Reg)
                bytes.Add(RegVal.ByteRepr);
            else bytes.AddRange(NumVal.Bytes);

            if (Relative)
                bytes.AddRange(Offset.Bytes);

            return bytes.ToArray();
        }
        public override uint GetBaseArgSize()
        {
            /*
            sizeof(const) = 4
            sizeof(reg) = size of reg
            sizeof(const+offset) = 4
            sizeof(reg+offset) = max(sizeof(reg), sizeof(offset))

            sizeof([??]) = 4
            sizeof([??]w) = 4
            sizeof([??]b) = 1
             */
            if (Indirect)
            {
                if (IndirectionSize.HasValue && IndirectionSize.Value == ArgSize.Byte) return (uint)ArgSize.Byte;
                else return (uint)ArgSize.Word;
            }
            if (SourceType == ArgSource.Const)
                return (uint)ArgSize.Word;
            else
            {
                uint regSize = (uint)RegVal.Size;
                uint offsetSize = Relative ? (uint)Offset.Size : 0;
                return regSize > offsetSize ? regSize : offsetSize;
            }
        }

        public static Tuple<Argument, string> FromString(string str, Dictionary<string, int> labelLines)
        {
            string inputStr = str;

            // [sizeof(]([)(Reg/#)[(+/-)(#)](])[)]
            bool indirect = str.StartsWith("[") && (str.EndsWith("]") || (str.Length >= 2 && str[str.Length - 2] == ']'));
            ArgSize? indirectionSize = null;
            if (indirect)
            {
                if (str.ToLower().EndsWith("b") || str.ToLower().EndsWith("w"))
                    indirectionSize = str.ToLower().EndsWith("b") ? ArgSize.Byte : ArgSize.Word;
                str = str.Substring(1, str.LastIndexOf(']') - 1);
            }
            bool relative = str.Contains('+') || str.Contains('-');
            bool negateOffset = relative && str.Contains('-');

            if (str.StartsWith("sizeof(") && str.EndsWith(")"))
            {
                string sizeOf = str.Substring("sizeof(".Length);
                sizeOf = sizeOf.Remove(sizeOf.Length - 1).Trim();
                Tuple<Argument, string> arg = SourceArg.FromString(sizeOf, labelLines);
                if (arg == null) return null;
                return new Tuple<Argument, string>(new SourceArg(new BaseNumber(arg.Item1.GetBaseArgSize()), false, null), "");
            }

            try
            {
                string baseVal = str,
                       offsetVal = "";
                if (relative)
                {
                    if (str.Contains('+'))
                    {
                        baseVal = str.Remove(str.IndexOf('+'));
                        if (str.IndexOf('+') == str.Length - 1)
                            throw new Error("A relative offset must come after a '+' or a '-'.");
                        offsetVal = str.Remove(0, baseVal.Length + 1);
                    }
                    else
                    {
                        baseVal = str.Remove(str.IndexOf('-')); // same down here
                        if (str.IndexOf('-') == str.Length - 1)
                            throw new Error("A relative offset must come after a '+' or a '-'.");
                        offsetVal = str.Remove(0, baseVal.Length + 1);
                    }
                }
                BaseNumber constVal = baseVal.ParseBaseNumber(labelLines);
                if (constVal != null)
                {
                    if (relative)
                    {
                        OffsetNumber offset = offsetVal.ParseOffsetNumber(labelLines);
                        if (offset == null) throw new Error("Could not parse '" + offsetVal + "' as a valid offset.");
                        if (negateOffset) offset.Negate();
                        return new Tuple<Argument, string>(new SourceArg(constVal, indirect, offset, indirectionSize), "");
                    }
                    return new Tuple<Argument, string>(new SourceArg(constVal, indirect, indirectionSize), "");
                }

                // not a number, so try to parse register.
                Register reg = Register.FromString(baseVal);
                if (relative)
                {
                    OffsetNumber offset = offsetVal.ParseOffsetNumber(labelLines);
                    if (offset == null) throw new Error("Could not parse '" + offsetVal + "' as a valid offset.");
                    if (negateOffset) offset.Negate();
                    if (reg != Register.None)
                        return new Tuple<Argument, string>(new SourceArg(reg, indirect, offset, indirectionSize), "");
                }
                if (reg != Register.None)
                    return new Tuple<Argument, string>(new SourceArg(reg, indirect, indirectionSize), "");


                // if not a register, then see if it's a label.
                if (labelLines.ContainsKey(baseVal))
                {
                    if (relative)
                    {
                        OffsetNumber offset = offsetVal.ParseOffsetNumber(labelLines);
                        if (offset == null) throw new Error("Could not parse '" + offsetVal + "' as a valid offset.");
                        if (negateOffset) offset.Negate();
                        return new Tuple<Argument, string>(new SourceArg(new BaseNumber(), indirect, offset, indirectionSize), baseVal);
                    }
                    return new Tuple<Argument, string>(new SourceArg(new BaseNumber(), indirect, indirectionSize), baseVal);
                }
                else throw new Error("Could not parse '" + inputStr + "' as a valid argument.");
            }
            catch (Error)
            {
                return null;
            }
        }

        public override string ToString()
        {
            string result = Indirect ? "[" : "";
            result += SourceType == ArgSource.Reg ? RegVal.ToString() : NumVal.ToString();
            if (Relative) result += "+" + Offset.ToString();
            if (Indirect) result += "]";
            return result;
        }
    }

    public enum DestType { Reg, IndReg, IndConst }
    public class DestArg : Argument
    {
        public DestType OpType { get; private set; }

        public BaseNumber NumVal { get; private set; }
        public Register RegVal { get; private set; }

        public bool Relative { get; private set; }
        public OffsetNumber Offset { get; private set; }

        public ArgSize? IndirectionSize { get; set; }

        private DestArg(DestType type, BaseNumber num, Register reg, bool relative, OffsetNumber offset, ArgSize? indirectionSize)
        {
            this.OpType = type;

            this.NumVal = num;
            this.RegVal = reg;

            this.Relative = relative;
            this.Offset = offset;

            this.IndirectionSize = indirectionSize;
        }

        public DestArg(BaseNumber constVal, ArgSize? indirectionSize)
            : this(DestType.IndConst, constVal, Register.None, false, new OffsetNumber(), indirectionSize) { }
        public DestArg(BaseNumber constVal, OffsetNumber offset, ArgSize? indirectionSize)
            : this(DestType.IndConst, constVal, Register.None, true, new OffsetNumber(), indirectionSize) { }

        public DestArg(Register reg, bool indirect, ArgSize? indirectionSize)
            : this(indirect ? DestType.IndReg : DestType.Reg, new BaseNumber(), reg, false, new OffsetNumber(), indirectionSize) { }
        public DestArg(Register reg, OffsetNumber offset, ArgSize? indirectionSize)
            : this(DestType.IndReg, new BaseNumber(), reg, true, offset, indirectionSize) { }

        public override ArgInfo GetArgType()
        {
            switch (OpType)
            {
                case DestType.Reg: return Relative ? new ArgInfo(false, false, Offset) : new ArgInfo(false, false);
                case DestType.IndReg: return Relative ? new ArgInfo(false, true, Offset) : new ArgInfo(false, true);
                case DestType.IndConst: return Relative ? new ArgInfo(true, true, Offset) : new ArgInfo(true, true);
            }
            throw new Exception("Missed a case in DestArg.GetArgType().");
        }

        public override byte[] GetArgData()
        {
            List<byte> bytes = new List<byte>();

            if (OpType == DestType.Reg || OpType == DestType.IndReg)
                bytes.Add(RegVal.ByteRepr);
            else bytes.AddRange(NumVal.Bytes);

            if (Relative)
                bytes.AddRange(Offset.Bytes);

            return bytes.ToArray();
        }
        public override uint GetBaseArgSize()
        {
            /*
            sizeof(reg) = size of reg
            sizeof(reg+offset) = max(sizeof(reg), sizeof(offset))

            sizeof([??]) = 4
            sizeof([??]w) = 4
            sizeof([??]b) = 1
             */
            if (OpType == DestType.IndReg || OpType == DestType.IndConst)
            {
                if (IndirectionSize.HasValue && IndirectionSize.Value == ArgSize.Byte) return (uint)ArgSize.Byte;
                else return (uint)ArgSize.Word;
            }
            else
            {
                uint regSize = (uint)RegVal.Size;
                uint offsetSize = Relative ? (uint)Offset.Size : 0;
                return regSize > offsetSize ? regSize : offsetSize;
            }
        }

        public static Tuple<Argument, string> FromString(string str, Dictionary<string, int> labelLines)
        {
            // hijack SourceArg.FromString and then limit the results, since DestArg can't be a direct constant.
            // it also can't be a direct register and be relative.

            Tuple<Argument, string> sourceArg = SourceArg.FromString(str, labelLines);
            if (sourceArg == null) return null;
            SourceArg source = sourceArg.Item1 as SourceArg;

            if (source.SourceType == ArgSource.Const && !source.Indirect)
                throw new Error("A destination argument cannot be a direct constant.");
            if (source.SourceType == ArgSource.Reg && !source.Indirect && source.Relative)
                throw new Error("A destination argument cannot be direct and relative to a register.");

            if (source.SourceType == ArgSource.Const)
            {
                if (source.Relative)
                    return new Tuple<Argument, string>(new DestArg(source.NumVal, source.Offset, source.IndirectionSize), sourceArg.Item2);
                else return new Tuple<Argument, string>(new DestArg(source.NumVal, source.IndirectionSize), sourceArg.Item2);
            }
            else
            {
                if (source.Relative)
                    return new Tuple<Argument, string>(new DestArg(source.RegVal, source.Offset, source.IndirectionSize), sourceArg.Item2);
                else return new Tuple<Argument, string>(new DestArg(source.RegVal, source.Indirect, source.IndirectionSize), sourceArg.Item2);
            }
        }

        public override string ToString()
        {
            bool indirect = OpType == DestType.IndConst || OpType == DestType.IndReg;
            string result = indirect ? "[" : "";
            result += OpType == DestType.IndConst ? NumVal.ToString() : RegVal.ToString();
            if (Relative) result += "+" + Offset.ToString();
            if (indirect) result += "]";
            return result;
        }
    }

    public class ArgInfo
    {
        public bool Number { get; private set; }
        public bool Indirect { get; private set; }
        public bool Relative { get; private set; }
        public OffsetNumber Offset { get; private set; }

        public ArgInfo(bool number, bool indirect)
        {
            Number = number;
            Indirect = indirect;
            Relative = false;
            Offset = null;
        }
        public ArgInfo(bool number, bool indirect, OffsetNumber offset)
        {
            Number = number;
            Indirect = indirect;
            Relative = true;
            Offset = offset;
        }

        public byte TypeByte
        {
            get
            {
                return (byte)((Number ? 1 : 0) | (Indirect ? 2 : 0));
            }
        }
    }
}
